package educrypt.analysis;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class Frequencies implements Iterable<FrequencyPair> {
	private List<FrequencyPair> percentages;
	private Map<String, Double> absoluteFrequency;

	public Frequencies(String[] symbols, double[] occurrences) {
		List<FrequencyPair> frequencyPairSet = new ArrayList<FrequencyPair>();
		for (int i = 0; i < symbols.length; i++) {
			FrequencyPair pair = new FrequencyPair(symbols[i], occurrences[i]);
			frequencyPairSet.add(pair);
		}
		this.percentages = frequencyPairSet;
	}

	public Frequencies(String[] symbols, double[] occurrences,
			double[] absoluteOcurrences) {
		List<FrequencyPair> frequencyPairSet = new ArrayList<FrequencyPair>();
		Map<String, Double> absoluteFrequency = new LinkedHashMap<String, Double>();
		for (int i = 0; i < symbols.length; i++) {
			FrequencyPair pair = new FrequencyPair(symbols[i], occurrences[i]);
			frequencyPairSet.add(pair);
			absoluteFrequency.put(symbols[i], absoluteOcurrences[i]);
		}
		this.percentages = frequencyPairSet;
		this.absoluteFrequency = absoluteFrequency;
	}

	public Frequencies(String[] symbols, Double[] occurrences,
			Double[] absoluteOcurrences) {
		List<FrequencyPair> frequencyPairSet = new ArrayList<FrequencyPair>();
		Map<String, Double> absoluteFrequency = new LinkedHashMap<String, Double>();
		for (int i = 0; i < symbols.length; i++) {
			FrequencyPair pair = new FrequencyPair(symbols[i], occurrences[i]);
			frequencyPairSet.add(pair);
			absoluteFrequency.put(symbols[i], absoluteOcurrences[i]);
		}
		this.percentages = frequencyPairSet;
		this.absoluteFrequency = absoluteFrequency;
	}

	private Frequencies(List<FrequencyPair> percentages) {
		this.percentages = percentages;
	}

	public FrequencyPair getFrequencyPairAt(int pos) {
		return percentages.get(pos);
	}

	public int getAbsoluteFrequency(String symbol) {
		double absoluteFreq = this.absoluteFrequency.get(symbol);
		return (int) absoluteFreq;
	}

	public int getAbsoluteFrequencyAt(int pos) {
		String symbol = this.percentages.get(pos).getSymbols();
		return getAbsoluteFrequency(symbol);
	}

	public FrequencyPair removeFrequencyPairAt(int pos) {
		FrequencyPair removedElement = this.percentages.remove(pos);
		if (absoluteFrequency != null) {
			this.absoluteFrequency.remove(removedElement.getSymbols());
		}
		return removedElement;
	}

	public String[] getNgrams() {
		FrequenciesSeparator separator = new FrequenciesSeparator();
		separator.separate(percentages);
		return separator.ngrams;
	}

	public Double[] getValues() {
		FrequenciesSeparator separator = new FrequenciesSeparator();
		separator.separate(percentages);
		return separator.values;
	}

	public Double[] getAbsoluteValues() {
		return absoluteFrequency.values().toArray(
				new Double[absoluteFrequency.size()]);
	}

	public String toString() {
		String stringRepresentation = "";
		for (FrequencyPair pair : this.percentages) {
			stringRepresentation += "[" + pair.toString() + "] ";
		}
		return stringRepresentation;
	}

	public Map<String, String> getCorrespondences(
			Frequencies frequenciesToCompare) {
		Map<String, String> correspondences = new LinkedHashMap<String, String>();

		Iterator<FrequencyPair> originalIterator = this.iterator();
		Iterator<FrequencyPair> toCompareIterator = frequenciesToCompare
				.iterator();
		while (originalIterator.hasNext() && toCompareIterator.hasNext()) {
			FrequencyPair originalPair = originalIterator.next();
			FrequencyPair comparedPair = toCompareIterator.next();
			correspondences.put(originalPair.getSymbols(),
					comparedPair.getSymbols());

		}
		return correspondences;
	}

	public List<String> getSymbolsWithMostOcurrences(int numberOfSymbols) {
		List<FrequencyPair> sortedPercentages = new ArrayList<FrequencyPair>(
				this.percentages);
		// Order by Percentage
		FrequencyPairByPercentage sortByPercentage = new FrequencyPairByPercentage();
		Collections.sort(sortedPercentages, sortByPercentage);

		List<String> symbolsWithMostOcurrences = new ArrayList<String>(
				numberOfSymbols);
		for (int i = sortedPercentages.size() - 1; i >= sortedPercentages
				.size() - numberOfSymbols; i--) {
			symbolsWithMostOcurrences
					.add(sortedPercentages.get(i).getSymbols());
		}
		return symbolsWithMostOcurrences;
	}

	// Obtain all possible list of sorted frequencies
	public List<Frequencies> getPermutations() {
		List<FrequencyPair> sortedPercentages = new ArrayList<FrequencyPair>(
				this.percentages);
		// Order by Percentage
		FrequencyPairByPercentage sortByPercentage = new FrequencyPairByPercentage();
		Collections.sort(sortedPercentages, sortByPercentage);

		// Decompose frequencies with the same value
		List<List<FrequencyPair>> frequenciesDecomposedByPercentage = decomposeByPercentage(sortedPercentages);

		//
		List<List<FrequencyPair>> permutations = new ArrayList<List<FrequencyPair>>();
		for (List<FrequencyPair> samePercentageFrequencies : frequenciesDecomposedByPercentage) {
			// Obtain permutations from decomposed frequencies
			List<List<FrequencyPair>> decomposeByPercentagePermutations = null;
			// if (samePercentageFrequencies.size() < 4) {
			decomposeByPercentagePermutations = permutate(samePercentageFrequencies);
			// } else {
			// decomposeByPercentagePermutations = new
			// ArrayList<List<FrequencyPair>>();
			// decomposeByPercentagePermutations
			// .add(samePercentageFrequencies);
			// }
			// Create all possible order from permutations
			permutations = addPermutationComponents(permutations,
					decomposeByPercentagePermutations);
		}

		List<Frequencies> permutatedFrequencies = new ArrayList<Frequencies>();
		for (List<FrequencyPair> permutation : permutations) {
			permutatedFrequencies.add(new Frequencies(permutation));
		}
		return permutatedFrequencies;
	}

	// return the maximum number of symbols which have the same percentage
	// used to determine if the cryptanalysis will succeed
	public int getMaxNumberOfSamePercentageOcurrences() {
		List<FrequencyPair> sortedPercentages = new ArrayList<FrequencyPair>(
				this.percentages);
		FrequencyPairByPercentage sortByPercentage = new FrequencyPairByPercentage();
		Collections.sort(sortedPercentages, sortByPercentage);
		int maxOcurrences = 1;
		int currentNumberOfOcurrences = 1;
		double prevPercentage = sortedPercentages.get(0).getPercentage();
		for (int i = 1; i < sortedPercentages.size(); i++) {
			double actPercentage = sortedPercentages.get(i).getPercentage();
			if (prevPercentage == actPercentage) {
				currentNumberOfOcurrences++;
				if (currentNumberOfOcurrences > maxOcurrences) {
					maxOcurrences = currentNumberOfOcurrences;
				}
			} else {
				currentNumberOfOcurrences = 1;
			}
			prevPercentage = actPercentage;
		}
		return maxOcurrences;
	}

	private List<List<FrequencyPair>> decomposeByPercentage(
			List<FrequencyPair> toDecompose) {

		assert !toDecompose.isEmpty();

		List<List<FrequencyPair>> frequenciesDecomposedByPercentage = new ArrayList<List<FrequencyPair>>();
		List<FrequencyPair> samePercentageFrequencies = new ArrayList<FrequencyPair>();
		samePercentageFrequencies.add(toDecompose.get(0));
		for (int i = 1; i < toDecompose.size(); i++) {
			FrequencyPair actFreq = toDecompose.get(i);
			FrequencyPair antFreq = samePercentageFrequencies.get(0);
			if (actFreq.getPercentage() != antFreq.getPercentage()) {
				frequenciesDecomposedByPercentage
						.add(samePercentageFrequencies);
				samePercentageFrequencies = new ArrayList<FrequencyPair>();
			}
			samePercentageFrequencies.add(actFreq);
			if (i == toDecompose.size() - 1) {
				frequenciesDecomposedByPercentage
						.add(samePercentageFrequencies);
			}
		}
		return frequenciesDecomposedByPercentage;

	}

	private static void permutate(List<FrequencyPair> aux,
			List<FrequencyPair> rep, List<List<FrequencyPair>> res) {
		if (rep.size() == 0) {
			// En aux tenemos el resultado
			List<FrequencyPair> temp = new ArrayList<FrequencyPair>(aux);
			res.add(temp);
		}
		for (int i = 0; i < rep.size(); i++) {
			// Retiramos el elemento de la lista
			FrequencyPair b = rep.remove(i);
			// Añadimos el elemento a aux solo para la recursion
			aux.add(b);
			permutate(aux, rep, res);
			aux.remove(b);
			// Restauramos la lista, añadiendo el elem. borrado
			rep.add(i, b);
		}
	}

	private static List<List<FrequencyPair>> permutate(List<FrequencyPair> rep) {
		List<FrequencyPair> aux = new ArrayList<FrequencyPair>();
		List<List<FrequencyPair>> res = new ArrayList<List<FrequencyPair>>();
		permutate(aux, rep, res);
		return res;
	}

	private List<List<FrequencyPair>> addPermutationComponents(
			List<List<FrequencyPair>> base,
			List<List<FrequencyPair>> newElements) {

		List<List<FrequencyPair>> newPermutations = new ArrayList<List<FrequencyPair>>();

		int baseSize = 0;
		baseSize = (base.isEmpty()) ? 1 : base.size();
		for (int i = 0; i < baseSize; i++) {
			for (int j = 0; j < newElements.size(); j++) {
				List<FrequencyPair> aux = (base.isEmpty()) ? new ArrayList<FrequencyPair>()
						: new ArrayList<FrequencyPair>(base.get(i));
				aux.addAll(newElements.get(j));
				newPermutations.add(aux);
			}
		}
		return newPermutations;
	}

	@Override
	public Iterator<FrequencyPair> iterator() {
		return percentages.iterator();
	}

	private class FrequenciesSeparator {
		String[] ngrams;
		Double[] values;

		void separate(List<FrequencyPair> percentages) {
			if (ngrams == null && values == null) {
				ngrams = new String[percentages.size()];
				values = new Double[percentages.size()];
				int index = 0;
				for (FrequencyPair pair : percentages) {
					ngrams[index] = pair.getSymbols();
					values[index] = pair.getPercentage();
					index++;
				}
			}
		}
	}
}
